#region Using References

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Practices.ServiceLocation;

#endregion

namespace Beaker.Services.Locator
{
	/// <summary>
	/// A default implementation for a manual service locator.
	/// You manually register all services at the start of the application.
	/// </summary>
	public class ManualServiceLocator
		: IServiceLocator
	{
		private readonly object _lock = new object();
		private readonly Dictionary<Type, Dictionary<string, object>> _services =
			new Dictionary<Type, Dictionary<string, object>>();

		#region Registration

		public void RegisterSingletonService<TService>( TService service, bool replace = false )
			where TService : class
		{
			Contract.Requires<ArgumentNullException>( service != null );

			RegisterSingletonService( string.Empty, service, replace );
		}

		public void RegisterSingletonService<TService>( string name, TService service, bool replace = false )
			where TService : class
		{
			Contract.Requires<ArgumentNullException>( name != null );
			Contract.Requires<ArgumentNullException>( service != null );

			lock( _lock )
			{
				// check if there is a sub-dictionary for this type
				Dictionary<string, object> services;
				if( !_services.TryGetValue( typeof( TService ), out services ) )
				{
					services = new Dictionary<string, object>();
					_services.Add( typeof( TService ), services );
				}

				if( !replace && services.ContainsKey( name ) )
					throw new InvalidOperationException( "Service of same type with same name is already registered" );

				services[name] = service;
			}
		}

		public void RegisterService<TService>( bool replace = false )
			where TService : class, new()
		{
			RegisterService<TService>( string.Empty, replace );
		}

		public void RegisterService<TService>( string name, bool replace = false )
			where TService : class, new()
		{
			Contract.Requires<ArgumentNullException>( name != null );

			lock( _lock )
			{
				// check if there is a sub-dictionary for this type
				Dictionary<string, object> services;
				if( !_services.TryGetValue( typeof( TService ), out services ) )
				{
					services = new Dictionary<string, object>();
					_services.Add( typeof( TService ), services );
				}

				if( !replace && services.ContainsKey( name ) )
					throw new InvalidOperationException( "Service of same type with same name is already registered" );

				services[name] = (Func<TService>)( () => new TService() );
			}
		}

		public void RegisterService<TService>( Func<TService> createInstance, bool replace = false )
			where TService : class
		{
			RegisterService( string.Empty, createInstance, replace );
		}

		public void RegisterService<TService>( string name, Func<TService> createInstance, bool replace = false )
			where TService : class
		{
			Contract.Requires<ArgumentNullException>( name != null );
			Contract.Requires<ArgumentNullException>( createInstance != null );

			lock( _lock )
			{
				// check if there is a sub-dictionary for this type
				Dictionary<string, object> services;
				if( !_services.TryGetValue( typeof( TService ), out services ) )
				{
					services = new Dictionary<string, object>();
					_services.Add( typeof( TService ), services );
				}

				if( !replace && services.ContainsKey( name ) )
					throw new InvalidOperationException( "Service of same type with same name is already registered" );

				services[name] = createInstance;
			}
		}

		#endregion
		#region IServiceLocator

		/// <summary>
		/// Gets the service object of the specified type.
		/// </summary>
		/// <returns>
		/// A service object of type <paramref name="serviceType"/>.-or- null if there is no service object of type <paramref name="serviceType"/>.
		/// </returns>
		/// <param name="serviceType">An object that specifies the type of service object to get. </param><filterpriority>2</filterpriority>
		object IServiceProvider.GetService( Type serviceType )
		{
			return GetInstance( serviceType, string.Empty );
		}

		protected virtual object Activate( object service )
		{
			Contract.Requires<ArgumentNullException>( service != null );

			// either, it is a delegate to execute, that will create the service
			var d = service as Delegate;
			if( d != null )
				return d.DynamicInvoke();

			// or it's a singleton service, to return
			return service;
		}

		/// <summary>
		/// Get an instance of the given <paramref name="serviceType"/>.
		/// </summary>
		/// <param name="serviceType">Type of object requested.</param>
		/// <returns>The requested service instance.</returns>
		public object GetInstance( Type serviceType )
		{
			return GetInstance( serviceType, string.Empty );
		}

		/// <summary>
		/// Get an instance of the given named <paramref name="serviceType"/>.
		/// </summary>
		/// <param name="serviceType">Type of object requested.</param>
		/// <param name="key">Name the object was registered with.</param>
		/// <returns>The requested service instance.</returns>
		public virtual object GetInstance( Type serviceType, string key )
		{
			//Contract.Requires<ArgumentNullException>( serviceType != null );
			//Contract.Requires<ArgumentNullException>( key != null );

			lock( _lock )
			{
				Dictionary<string, object> services;
				if( !_services.TryGetValue( serviceType, out services ) )
					return null;

				object service;
				if( !services.TryGetValue( key, out service ) )
					return null;

				return Activate( service );
			}
		}

		/// <summary>
		/// Get all instances of the given <paramref name="serviceType"/> currently
		/// registered in the container.
		/// </summary>
		/// <param name="serviceType">Type of object requested.</param>
		/// <returns>A sequence of instances of the requested <paramref name="serviceType"/>.</returns>
		public virtual IEnumerable<object> GetAllInstances( Type serviceType )
		{
			//Contract.Requires<ArgumentNullException>( serviceType != null );
			Contract.Ensures( Contract.Result<IEnumerable<object>>() != null );

			lock( _lock )
			{
				Dictionary<string, object> services;
				if( !_services.TryGetValue( serviceType, out services ) )
					return new object[0];

				return
					from object service in services.Values
					select Activate( service );
			}
		}

		/// <summary>
		/// Get an instance of the given <typeparamref name="TService"/>.
		/// </summary>
		/// <typeparam name="TService">Type of object requested.</typeparam>
		/// <returns>The requested service instance.</returns>
		public TService GetInstance<TService>()
		{
			return (TService)GetInstance( typeof( TService ), string.Empty );
		}

		/// <summary>
		/// Get an instance of the given named <typeparamref name="TService"/>.
		/// </summary>
		/// <typeparam name="TService">Type of object requested.</typeparam>
		/// <param name="key">Name the object was registered with.</param>
		/// <returns>The requested service instance.</returns>
		public TService GetInstance<TService>( string key )
		{
			return (TService)GetInstance( typeof( TService ), key );
		}

		/// <summary>
		/// Get all instances of the given <typeparamref name="TService"/> currently
		/// registered in the container.
		/// </summary>
		/// <typeparam name="TService">Type of object requested.</typeparam>
		/// <returns>A sequence of instances of the requested <typeparamref name="TService"/>.</returns>
		public IEnumerable<TService> GetAllInstances<TService>()
		{
			Contract.Ensures( Contract.Result<IEnumerable<object>>() != null );

			return GetAllInstances( typeof( TService ) )
				.OfType<TService>();
		}

		#endregion
	}
}